var events = require('events');
var util = require('util');
var ElasticSearchClient = require('elasticsearchclient');
var config = require('./config');

var serverOptions = {
	host : config.elServerHost,
	port : config.elServerPort
};

function IndexSearcher() {
	this.searchClient = new ElasticSearchClient(serverOptions);
	events.EventEmitter.call(this);
}

util.inherits(IndexSearcher, events.EventEmitter);

IndexSearcher.prototype.constructor = IndexSearcher;
IndexSearcher.prototype.searchClient = null;

IndexSearcher.prototype.search = function(type, queryObj, callback) {
	var self = this;

	// parse queryObj to elasticsearch syntax
	try {
		var elQueryObj = parseQueryObj(queryObj);
	} catch (e) {
		var errorResult = parseErrorResult(e.toString());
		if (typeof self.callback == 'function') {
			self.callback(errorResult);
		} else {
			self.emit('finish', errorResult);
		}
		return;
	}

	// search document
	this.searchClient.search(config.indexName, type, elQueryObj, function(err,
			data) {
		if(err != null){
			if (typeof self.callback == 'function') {
				self.callback(err, null);
			} else {
				self.emit('error', err);
			}
		} else {
			var result = parseQueryResult(data);
			if (typeof self.callback == 'function') {
				self.callback(null, result);
			} else {
				self.emit('finish', result);
			}
		}
	});
	
	return this;
};

function parseQueryResult(queryResultStr){
	var queryResult = JSON.parse(queryResultStr);
	console.log(queryResult);
	var result = {};
	result['data'] = {};
	result['data']['total'] = queryResult.hits['total'];
	result['data']['results'] = [];
	queryResult['hits']['hits'].forEach(function(rowData){
		result['data']['results'].push(rowData['_source']);
	});
	return result;
}

function parseErrorResult(error){
	var result = {};
	result['error'] = {};
	result['error']['orchestrationLayer'] = error;
	return result;
}

/**
 * Convert queryObj to elasticsearch format query string
 * 
 * @param queryObj
 */
function parseQueryObj(queryObj) {
	var finalELQueryObj = {};
	for ( var p in queryObj) {
		if (p == 'sort') {
			var sorts = [];
			queryObj[p].forEach(function(oneSort){
				console.log(oneSort);
				for(var key in oneSort){
					var order = {};
					order['order'] = oneSort[key];
					var sort = {};
					sort[key] = order;
					sorts.push(sort);
				}
			});
			finalELQueryObj['sort'] = sorts;
		} else if (p == 'start') {
			finalELQueryObj['from'] = queryObj[p];
		} else if (p == 'fetch') {
			finalELQueryObj['size'] = queryObj[p];
		} else if (p == 'filter') {
			var filterObj = queryObj[p];
			var filterQuery = {};
			for(var key in filterObj){
				if(!isBooleanOperator(key)){
					throw new Error("Should be bool operator, but is: " + key);
				}
				var boolQueries = parseBoolQuery(filterObj[key]);
				boolOperators[key](boolQueries, filterQuery);
				break;
			}
			finalELQueryObj['query'] = {};
			finalELQueryObj['query']['filtered'] = {};
			finalELQueryObj['query']['filtered']['filter'] = filterQuery;
		} else {
			throw new Error("Unknown segment: " + p);
		}

	}
	return finalELQueryObj;
}

function parseBoolQuery(queryObj) {
	var boolQuery = [];
	queryObj.forEach(function(criteria) {
		if (!isObject(criteria)) {
			throw new Error("Criteria should be object, but is: " + criteria);
		}
		var keys = Object.keys(criteria);
		if (keys.length > 1) {
			throw new Error("Only one criteria allowed.");
		}
		var key = keys[0];
		var query;
		if (isBooleanOperator(key)) {
			// bool criteria
			var logicQueries = parseBoolQuery(criteria[key]);
			query = {};
			boolOperators[key](logicQueries, query);
		} else {
			query = parseLogicQuery(criteria);
		}
		boolQuery.push(query);
	});
	
	return boolQuery;
}

function parseLogicQuery(queryObj) {
	if (!isObject(queryObj)) {
		throw new Error("queryObj should be object, but is: " + queryObj);
	}
	var keys = Object.keys(queryObj);
	var field = keys[0];
	// key is field name, the pattern is {field : 'value'} or {field : {'$starWith' : 'value'}}
	var value = queryObj[field];
	var operator;
	if (isObject(value)) {
		operator = Object.keys(value)[0];
		value = value[operator];
	} else {
		// a match query
		operator = '$equal';
	}
	
	var elQuery = {};
	logicOperators[operator](field, value, elQuery);
	return elQuery;
}

var boolOperators = {
	'$and' : function(boolQueryObjs, queryObj){
		queryObj['bool'] = {'must' : boolQueryObjs};
	},
	'$or' : function(boolQueryObjs, queryObj){
		queryObj['bool'] = {'should' : boolQueryObjs};
	}
};
var logicOperators = {
	'$equal' : function(field, value, queryObj) {
		queryObj['term'] = {};
		queryObj['term'][field] = value;
	},
	'$startWith' : function(field, value, queryObj) {
		queryObj['prefix'] = {};
		queryObj['prefix'][field] = value;
	},
	'$contain' : function(field, value, queryObj) {
		queryObj['prefix'] = {};
		queryObj['prefix'][field] = value;
	},
	'$in' : function(field, value, queryObj) {
		queryObj[field] = value;
	},
	'$gt' : function(field, value, queryObj) {
		queryObj['range'] = {};
		queryObj['range'][field] = {};
		queryObj['range'][field]['gt'] = value;
	},
	'$lt' : function(field, value, queryObj) {
		queryObj['range'] = {};
		queryObj['range'][field] = {};
		queryObj['range'][field]['lt'] = value;
	}
};

function isBooleanOperator(opt) {
	return boolOperators.hasOwnProperty(opt);
}

function isValidOperator(opt) {
	return logicOperators.hasOwnProperty(opt);
}

function isObject(obj) {
	return (typeof obj == 'object');
}

module.exports.IndexSearcher = IndexSearcher;